package org.rm.beans;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.rm.entidades.ExParcial;
import org.rm.entidades.Exp;
import org.rm.entidades.ExperienciaBase;
import org.rm.entidades.Jugadores;
import org.rm.entidades.Partida;
import org.rm.entidades.jpa.ExParcialController;
import org.rm.entidades.jpa.JpaExperiencia;
import org.rm.entidades.jpa.JugadoresJpaController;
import org.rm.entidades.jpa.PartidaJpaController;
import org.rm.entidades.jpa.exceptions.NonexistentEntityException;

/**
 * Clase para gestionar las experiencias
 * @author ffbeltran
 */
public class ControladorExperiencia extends ControladorEspecifico {

    private Map<String,ExperienciaBase> entidadesBase;
    private Map<String,List> listasExperiencias;
    private Partida partida;

    /**
     * Método que sirve para modificar una experiencia determinada. Debe recibir
     * dos parámetros junto con la llamada:
     * <ul>
     *  <li>
     *      'id': identificador de la experiencia que está modificándose.
     *  </li>
     *  <li>
     *      'experiencia': cadena que identifica el tipo de experiencia. Debe coincidir
     *      con alguno de los valores devueltos por {@link ControladorUtil#getNombresEntidades() }
     *  </li>
     * </ul>
     * Recupera la experiencia que ha modificado el usuario y la experiencia
     * anterior (aún está en la base de datos). A continuación calcula los valores
     * de experiencia para cada jugador (restándole el valor anterior y sumándole
     * el nuevo) y actualiza su lista de experiencias
     * @return la cadena 'experiencias'
     * @see ControladorUtil#getParam(java.lang.String)
     */
    public String modificaExperiencia() {
        Integer id = Integer.valueOf(ControladorUtil.getParam("id"));
        String name = ControladorUtil.getParam("experiencia");
        Class clazz = ControladorUtil.getEntidad(name);
        ExperienciaBase antiguo = null;
        ExperienciaBase nuevo = null;
        List experiencias = listasExperiencias.get(name);
        for (Iterator it = experiencias.iterator(); it.hasNext();) {
            nuevo = (ExperienciaBase) it.next();
            if (nuevo.getId().equals(id)) {
                try {
                    antiguo = JpaExperiencia.findExperienciaBase(clazz, id);
                    if(antiguo.getExp() == null)
                    	antiguo.actualizaExp();
                    antiguo.setExp( antiguo.getExp() * -1);
                    nuevo.actualizaExp();
                    JpaExperiencia.edit(nuevo);
                    listasExperiencias.put(name,
                            JpaExperiencia.findExperienciaBaseEntities(clazz, partida.getId()));
                    List<ExperienciaBase> expList = new ArrayList<ExperienciaBase>();
                    expList.add(antiguo);
                    expList.add(nuevo);
                    calculaExperiencia(expList);
                } catch (NonexistentEntityException ex) {
                    ex.printStackTrace();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                break;
            }
        }
        return "experiencias";
    }

    /**
     * Método que sirve para eliminar una experiencia determinada. Debe recibir
     * dos parámetros junto con la llamada:
     * <ul>
     *  <li>
     *      'id': identificador de la experiencia que está modificándose.
     *  </li>
     *  <li>
     *      'experiencia': cadena que identifica el tipo de experiencia. Debe coincidir
     *      con alguno de los valores devueltos por {@link ControladorUtil#getNombresEntidades() }
     *  </li>
     * </ul>
     * Elimina la experiencia de la base de datos, resta su valor de experiencia
     * a cada jugador afectado y actualiza su lista de experiencias
     * @return la cadena 'experiencias'
     * @see ControladorUtil#getParam(java.lang.String)
     */
    public String eliminaExperiencia() {
        Integer id = Integer.valueOf(ControladorUtil.getParam("id"));
        String name = ControladorUtil.getParam("experiencia");
        Class clazz = ControladorUtil.getEntidad(name);
        ExperienciaBase exp = JpaExperiencia.findExperienciaBase(clazz, id);
        if (exp != null) {
            exp.actualizaExp();
            exp.setExp(exp.getExp() * -1);
        }
        try {
            JpaExperiencia.destroy(clazz, id);
            listasExperiencias.put(name,
                    JpaExperiencia.findExperienciaBaseEntities(clazz, partida.getId()));
            calculaExperiencia(exp);
        } catch (NonexistentEntityException ex) {
            ex.printStackTrace();
        }
        return "experiencias";
    }

    /**
     * Método que sirve para crear una nueva experiencia. Debe recibir un sólo
     * parámetro, 'experiencia', que determina el tipo de experiencia que se
     * va a crear. Debe coincidir con algún valor devuelto por el método
     * {@link ControladorUtil#getNombresEntidades() }
     * Recupera la experiencia que ha modificado el usuario, la
     * almacena en la BBDD y a continuación recalcula su lista de experiencias
     * @return la cadena 'experiencias'
     * @see ControladorUtil#getParam(java.lang.String)
     */
    public String nuevaExperiencia() {
        String name = ControladorUtil.getParam("experiencia");
        Class clazz = ControladorUtil.getEntidad(name);
        ExperienciaBase exp = entidadesBase.get(name);
        exp.setId(null);
        exp.setIdPartida(partida.getId());
        exp.actualizaExp();
        JpaExperiencia.create(exp);
        listasExperiencias.put(name,
                JpaExperiencia.findExperienciaBaseEntities(clazz, partida.getId()));
        calculaExperiencia(exp);
        return "experiencias";
    }

    /**
     * Devuelve un mapa donde las claves son los nombres de experiencias que
     * coinciden con {@link ControladorUtil#getNombresEntidades() } y los valores
     * son listas de experiencias
     * @return mapa ['tipoExperiencia' => lista experiencias]
     */
    public Map<String,List> getListasExperiencias() {
        return listasExperiencias;
    }

    /**
     * Devuelve un mapa donde las claves son los nombres de experiencias que
     * coinciden con {@link ControladorUtil#getNombresEntidades() } y los valores
     * son objetos de tipo {@code ExperienciaBase} utilizados por la vista
     * para que el usuario pueda crear nuevas experiencias.
     * Se trata de objetos vacíos cuyas propiedades rellena el usuario a través
     * de la vista
     * @return mapa ['tipoExperiencia' => {@code ExperienciaBase}]
     */
    public Map<String,ExperienciaBase> getEntidadesBase() {
        return entidadesBase;
    }

    /**
     * Método que devuelve un mapa donde las claves son los nombres de experiencias que
     * coinciden con {@link ControladorUtil#getNombresEntidades() } y los valores
     * son el número de experiencias para ese tipo. <br/>
     * {@code getTamanyoListas().get('critico')} es igual a
     * {@code getListasExperiencias().get('critico').size()}
     * @return mapa ['tipoExperiencia' => entero]
     */
    public Map<String,Integer> getTamanyoListas() {
        Map<String,Integer> numElemlistas = new HashMap<String,Integer>();
        for (String nombre : listasExperiencias.keySet()) {
            numElemlistas.put(nombre, listasExperiencias.get(nombre).size());
        }
        return numElemlistas;
    }

    /**
     * Método que es llamado por el formulario de seleccionar partida. Simplemente
     * inicializa las listas y los beans para la partida seleccionada mediante
     * su método setter. Si partida es null, selecciona la última partida
     * mediante el método {@link PartidaJpaController#getMaxIdPartida() }
     * @return la cadena 'experiencias'
     * @see #setPartida(org.rm.entidades.Partida)
     */
    public String seleccionaPartida() {
        if(partida == null)
            partida = PartidaJpaController.findPartida(PartidaJpaController.getMaxIdPartida());
        inicializaListas();
        inicializaBeans();
        return "experiencias";
    }
    
    private void calculaExperiencia(Exp experiencia) {
        List<Exp> expList = new ArrayList<Exp>();
        expList.add(experiencia);
        calculaExperiencia(expList);
    }

    private void calculaExperiencia(List<? extends Exp> experiencia) {
    	Map<Partida, Integer> expCompartida = new HashMap<Partida, Integer>();
    	
    	for (Partida aux : PartidaJpaController.findPartidaEntities()) {
    		expCompartida.put(aux, 0);
		}
        
    	Map<Integer, Jugadores> jugadores = form.getMapaJugadores();
    	
        for (Integer idJugador : jugadores.keySet()) {
            Jugadores jugadorActual = jugadores.get(idJugador);
            for (Iterator<? extends Exp> it2 = experiencia.iterator(); it2.hasNext();) {
                Exp exp = it2.next();
                if(exp.getJugador().equals(jugadorActual)) {
                    int expSign = (exp.getExp()>=0)?1:-1;
                    int expAbs = Math.abs(exp.getExp());
                    int expComp = (int) Math.ceil(((double) expAbs * exp.getCompartido() / 100));
                    int expJugador = (expAbs - expComp) * expSign;
                    expComp = (int) Math.ceil((double) expComp / jugadores.size());
                    Partida p = PartidaJpaController.findPartida(exp.getIdPartida());
                    addExpJugador(jugadorActual, expJugador, p);
                    Integer aux = expCompartida.get(p);
                    expCompartida.put(p,aux + (expComp * expSign));
                }
            }
        }
        
        for (Partida p : expCompartida.keySet()) {
			int expCompartidaGeneral = expCompartida.get(p);
			for (Integer idJugador : jugadores.keySet()) {
                Jugadores jugadorActual = jugadores.get(idJugador);
				addExpJugador(jugadorActual, expCompartidaGeneral, p);
			}
		}
    }

    private void addExpJugador(Jugadores jugador, int exp,
            Partida partida) {
        boolean exist = false;
        ExParcial exParcial;

        for (ExParcial aux : jugador.getExperiencia()) {
            if (aux.getPartida().equals(partida)) {
                exParcial = aux;
                exParcial.setExperiencia(exParcial.getExperiencia() + exp);
                exist = true;
                break;
            }
        }
        if (!exist) {
            exParcial = new ExParcial(exp, partida);
            ExParcialController.create(exParcial);
            jugador.getExperiencia().add(exParcial);
        }
        try {
            JugadoresJpaController.edit(jugador);
        } catch (NonexistentEntityException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void inicializaListas() {
        listasExperiencias = new HashMap<String,List>();
        for (String nombre : ControladorUtil.getNombresEntidades()) {
            Class clase = ControladorUtil.getEntidad(nombre);
            List<ExperienciaBase> aux = JpaExperiencia.findExperienciaBaseEntities(clase, partida.getId());
            if(aux == null)
            	aux = Collections.EMPTY_LIST;
            listasExperiencias.put(nombre, aux);
        }
    }

    private void inicializaBeans() {
        entidadesBase = new HashMap<String,ExperienciaBase>();
        for (String nombre : ControladorUtil.getNombresEntidades()) {
            try {
                Class clase = ControladorUtil.getEntidad(nombre);
                entidadesBase.put(nombre, (ExperienciaBase) clase.newInstance());
            } catch (InstantiationException ex) {
                ex.printStackTrace();
            } catch (IllegalAccessException ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * Método setter para el atributo partida
     * @param partida
     */
    public void setPartida(Partida partida) {
		this.partida = partida;
	}

    /**
     * Método getter para el atributo partida
     * @return el atributo partida
     */
    public Partida getPartida() {
		return partida;
	}

}
